package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	// array 数组
	/**
	数组存放的是固定长度、相同类型的数据，而且这些存放的元素是连续的。所存放的数据类型没有限制，可以是整型、字符串甚至自定义。

	*/
	// 创建数组 way1
	arr := [3]string{"A", "b", "C"}
	fmt.Println(arr, arr[0]) // 提取数组中的元素

	// 创建数组 way2 编译器会自动计算数组长度
	arr = [...]string{"A", "b", "C"}
	/**
	需要注意的是，上面创建数组的方式是只针对特定索引元素初始化的情况，就不适合了
	*/
	// - 数组的类型是 长度 + 元素类型 ，相同元素不同长度也不算一个类型的数组，因此这里不可以在用 arr来接受长度为5的数组
	// arr = [5]string{1:"b",3:"d"}
	arr1 := [5]string{1: "b", 3: "d"} // 表示创建一个长度为5的数组，索引为1和3的地方分别为b和d，其他地方填充string类型的零值
	fmt.Println(arr1)

	// 数组循环
	// way1
	for i := 0; i < 5; i++ {
		fmt.Printf("数组索引:%d,对应值:%s\n", i, arr1[i])
	}

	// way2 range 遍历可迭代对象，i是索引下标，s是值
	for i, s := range arr1 {
		fmt.Print(i, s, " ")
	}
	/**
	相比传统的 for 循环，for range 要更简洁，如果返回的值用不到，可以使用 _ 下划线丢弃，如下面的代码所示：
	*/
	for _, v := range arr {
		fmt.Printf("对应值:%s\n", v)
	}

	// 切片 切片和数组类似，可以把它理解为动态数组。切片是基于数组实现的，它的底层就是一个数组。对数组任意分隔，就可以得到一个切片。现在我们通过一个例子来更好地理解它，同样还是基于上述例子的 array。
	/**
	切片是一个具备三个字段的数据结构，分别是指向数组的指针 data，长度 len 和容量 cap
		- 如果是用数组创建的切片，切片底层用的也是 array 数组的data,所以改变切片中的值也会改变数组中的值，但是经过切片后，切片的索引范围改变了
	*/

	// 创建切片 way1 基于数组生成切片
	/**
	- 如果是用数组创建的切片，切片底层用的也是 array 数组的data,所以改变切片中的值也会改变数组中的值，但是经过切片后，切片的索引范围改变了
	*/
	array := [5]string{"a", "b", "c", "d", "e"}
	slice := array[2:5]
	fmt.Println(slice, array[2], slice[0]) // array[2], slice[0] 是相同的c在数组和切片中的索引下标位置

	slice[0] = "F" // 改变切片中的值，数组中也会对应改变
	fmt.Println(array, "|", slice)

	// - 如果对容量和长度一样的切片进行增加元素的操作，slice会自动进行扩容，cap会增加一倍，然后len + 1
	// 并且slice在进行扩容之后,会返回一个新的内存空间的slice，修改这个slice中的数据并不会改变原来array和slice中的数据
	// 在平时的工作中，我们用到最多的还是切片而不是数组，因为切片比较灵活

	// 综上所述，建议创建slice时候cap和len相同
	fmt.Println("old:", cap(slice), len(slice), slice, array)
	slice = append(slice, "x")
	fmt.Println("new:", cap(slice), len(slice), slice, array)
	slice[1] = "p"
	slice = append(slice, "y")
	fmt.Println("new2:", cap(slice), len(slice), slice, array)
	slice = append(slice, "u", "k", "m")
	fmt.Println("new2:", cap(slice), len(slice), slice, array)

	// 创建slice way2
	slice = []string{"a", "b", "c", "d", "e"}
	fmt.Println("slice:", cap(slice), len(slice), slice)

	//追加一个元素
	slice2 := append(slice, "f")
	fmt.Println("slice2:", cap(slice2), len(slice2), slice2)
	//多加多个元素
	slice2 = append(slice, "f", "g")
	fmt.Println("slice2:", cap(slice2), len(slice2), slice2)
	//追加另一个切片
	slice2 = append(slice, slice...)
	fmt.Println("slice2:", cap(slice2), len(slice2), slice2)

	// 切片元素循环 ，和 array相同
	for i, i2 := range slice2 {
		fmt.Print(i, i2, " ")
	}

	// map映射
	/**
	在 Go 语言中，map 是一个无序的 K-V 键值对集合，结构为 map[K]V。其中 K 对应 Key，V 对应 Value。map 中所有的 Key 必须具有相同的类型，Value 也同样，但 Key 和 Value 的类型可以不同。
	此外，Key 的类型必须支持 == 比较运算符，这样才可以判断它是否存在，并保证 Key 的唯一。
	*/
	fmt.Println()
	// 创建一个map way1
	nameAgeMap := make(map[string]string)
	// 更新元素
	nameAgeMap["edgjungle"] = "clearlove"
	// way2
	nameAgeMap = map[string]string{"edgad": "viper"}

	// 获取元素
	fmt.Println(nameAgeMap["edgad"])
	// 获取存着的元素
	v := nameAgeMap["edgad"]
	fmt.Println("exists:", v)

	// 当元素不存在时,返回的时元素的零值
	v = nameAgeMap["edgmid"]
	fmt.Println("notexits:", v)

	// 也可以通过第二个返回的flag判断key是不是存在
	nameAgeMap = map[string]string{}
	nameAgeMap["sup"] = "meiko"

	v, ok := nameAgeMap["sup"]
	fmt.Println(v, ok)
	v, ok = nameAgeMap["top"]
	fmt.Println(v, ok)

	// 遍历map
	/**
	map 是一个键值对集合，它同样可以被遍历，在 Go 语言中，map 的遍历使用 for range 循环。
	对于 map，for range 返回两个值：
	第一个是 map 的 Key；
	第二个是 map 的 Value。
	我们用下面的代码进行演示：
	*/
	//测试 for range

	//nameAgeMap2 := map[string]int{}
	var nameAgeMap2 map[string]int = map[string]int{}
	nameAgeMap2["飞雪无情"] = 20
	nameAgeMap2["飞雪无情1"] = 21
	nameAgeMap2["飞雪无情2"] = 22
	for k, v := range nameAgeMap2 {
		fmt.Println("Key is", k, ",Value is", v)
	}
	/**
	需要注意的是 map 的遍历是无序的，也就是说你每次遍历，键值对的顺序可能会不一样。如果想按顺序遍历，可以先获取所有的 Key，并对 Key 排序，然后根据排序好的 Key 获取对应的 Value。
	*/

	// map的大小
	// map的大小指的就是键值对的个数
	fmt.Println(len(nameAgeMap2))

	// string 和 []byte
	// 字符串 string 也是一个不可变的字节序列，所以可以直接转为字节切片 []byte
	s := "hello world"
	bs := []byte(s)
	fmt.Println(len(bs), cap(bs), bs)
	fmt.Println(utf8.RuneCount(bs))

	// 字符串的长度
	// go中字符串的长度指的是字节数，一个utf-8的中文有三个字节，所以s1的字节数是 2*3+5 = 11
	s1 := "你好scout"
	fmt.Println("s1len:", len(s1))

	// 如果要统计utf-8编码下unicode字符的个数，则使用
	// 而使用 for range 对字符串进行循环时，也恰好是按照 unicode 字符进行循环的，所以对于字符串 s1 来说，循环了 7 次。
	fmt.Println(utf8.RuneCountInString(s1))

	for i, i2 := range s1 {
		// 注意这里i的索引变成了 字节数组中的 索引下标
		fmt.Println(i, i2)
	}

}
